<?php

namespace api\components\dataCenter;

use Closure;
use Exception;
use SplFileObject;
use Yii;

class ExportToCsv
{

    //正常导出
    const RESULT_TYPE_COMMON = 'common';
    //离线导出
    const RESULT_TYPE_OFFLINE = 'offline';

    /**
     * @var SearchEngine
     */
    protected $search_engine;

    /**
     * @var string
     */
    private $result_type;

    /**
     * @var SplFileObject|string
     */
    private $result_data;

    /**
     * ExportToCsv constructor.
     * @param SearchEngine $search_engine
     */
    public function __construct(SearchEngine $search_engine)
    {
        $this->search_engine = $search_engine;
    }

    /**
     * 多线程下载limit数量 默认600条
     * @var int
     */
    protected $multiple_http_download_limit_num = 600;

    /**
     * 离线下载最小数据量
     * 默认10000（数据中台 大于10000的数据无法取出）
     * @var int
     */
    protected $offline_download_min_num = 10000;

    /**
     * 离线下载最大数据量
     * 默认1000000
     * @var int
     */
    protected $offline_download_max_num = 1000000;

    private function getOfflineDownloadToken()
    {
        return Yii::$app->params['asycn_download_api']['key'];
    }

    /**
     * 设置离线下载最低条数
     * 默认为 10000，即条数>10000条则使用离线下载
     * @param int $offline_download_min_num
     * @return ExportToCsv
     */
    public function setOfflineDownloadMinNum(int $offline_download_min_num): ExportToCsv
    {
        $this->offline_download_min_num = $offline_download_min_num;
        return $this;
    }

    /**
     * 设置离线下载最高条数
     * 默认为 1000000，即条数>1000000条则报错无法下载
     * @param int $offline_download_max_num
     * @return ExportToCsv
     */
    public function setOfflineDownloadMaxNum(int $offline_download_max_num): ExportToCsv
    {
        $this->offline_download_max_num = $offline_download_max_num;
        return $this;
    }

    /**
     * 设置多线程下载每线程条数
     * 默认为 600，即每线程获取600条数据
     * @param int $multiple_http_download_limit_num
     * @return ExportToCsv
     */
    public function setMultipleHttpDownloadLimitNum(int $multiple_http_download_limit_num): ExportToCsv
    {
        $this->multiple_http_download_limit_num = $multiple_http_download_limit_num;
        return $this;
    }

    /**
     * 导出主方法
     * @param array $export_field
     * @param string|null $file_name
     * @return self
     * @throws Exception
     */
    public function export(array $export_field, $uid, ?string $file_name = null): self
    {
        if (empty($export_field)) {
            throw new Exception('未选择任何导出字段');
        }

        foreach ($export_field as $field) {
            if (!isset($field['field']) || !$field['field']) {
                continue;
            }
            $this->search_engine->addSelect($field['select_field'] ?? $field['field']);
        }

        $count = $this->search_engine->count();
        $params = $this->search_engine->getParams();
        if ($count < $this->offline_download_min_num) {
            //10000条以下 多线程下载导出
            $file = $this->newCsvFileObj($file_name);
            $file->fwrite(chr(0xEF) . chr(0xBB) . chr(0xBF));
            $file->fputcsv(array_column($export_field, 'title'));

            $params['limit'] = $this->multiple_http_download_limit_num;
            $url = DATA_CENTER_HOST . SearchEngine::COMMON_SEARCH_URL . '?' . http_build_query($params);
            $reponse = Yii::$app->curl->get($url);
            if ($reponse['data']['messages']) {
                foreach ($reponse['data']['messages'] as $row) {
                    $file->fputcsv(DataCenterSearchModel::handleData($row, $export_field));
                }
            }
            $this->setResult(self::RESULT_TYPE_COMMON, $file);
            return $this;
        } elseif ($count < $this->offline_download_max_num) {
            $params['token'] = $this->getOfflineDownloadToken();
            $params['create_user'] = $uid;
            //进入离线下载流程
            $result = (new AsycnDownloadService())->createDownloadTask($params);
            if (isset($result['result']) && $result['result'] == 'success' && isset($result['task_id']) && $result['task_id']) {
                $this->setResult(self::RESULT_TYPE_OFFLINE, $result['task_id']);
                return $this;
            } else {
                throw new Exception($result['result'] ?? '离线下载网络错误');
            }
        } else {
            //导出条数 > 1000000
            throw new Exception('导出条数过多');
        }
    }

    /**
     * 离线导出
     * @param $task_id
     * @param array $export_field
     * @return SplFileObject
     * @throws Exception
     */
    public static function exportByAsyncTask($task_id, array $export_field): SplFileObject
    {
        if (empty($export_field)) {
            throw new Exception('未选择任何导出字段');
        }
        $file = self::newCsvFileObj();
        $file->fwrite(chr(0xEF) . chr(0xBB) . chr(0xBF));
        $file->fputcsv(array_column($export_field, 'title'));
        foreach ((new AsycnDownloadService())->downloadData($task_id) as $line) {
            $file->fputcsv(DataCenterSearchModel::handleData(json_decode($line, true), $export_field));
        }
        return $file;
    }

    /**
     * 生成 SplFileObject文件对象
     * @param string|null $file_name
     * @return SplFileObject
     */
    private static function newCsvFileObj(?string $file_name = null): SplFileObject
    {
        $date = date('Y-m-d');
        $runtime = Yii::$app->getRuntimePath();
        $file_name = $file_name ?? "导出结果_{$date}_" . uniqid() . mt_rand(100, 999) . '.csv';

        $file_name = "exportfile" . DIRECTORY_SEPARATOR . "$date" . DIRECTORY_SEPARATOR . trim($file_name, DIRECTORY_SEPARATOR);
        $file_path_info = pathinfo($file_name);

        $file_path = '';
        foreach (explode(DIRECTORY_SEPARATOR , $file_path_info['dirname']) as $dir_item) {
            $file_path = trim("$file_path" . DIRECTORY_SEPARATOR . "$dir_item", DIRECTORY_SEPARATOR);
            !is_dir("$runtime".DIRECTORY_SEPARATOR ."$file_path") && mkdir("$runtime/$file_path");
        }
        return new SplFileObject("$runtime" .DIRECTORY_SEPARATOR."$file_path" .DIRECTORY_SEPARATOR . $file_path_info['basename'], 'x+');
    }

    /**
     * @param string $result_type
     * @param SplFileObject|string $result_data
     */
    private function setResult(string $result_type, $result_data): void
    {
        $this->result_type = $result_type;
        $this->result_data = $result_data;
    }

    /**
     * 获取导出结果
     * @return array
     * demo:
     *  [ common , SplFileObject ] 正常导出 返回一个SplFileObject对象，后续可以直接返回前端下载地址或者选择上传至文件服务器
     *  [ offline , task_id (string) ] 离线导出 返回一个task_id，后续可以通过 exportByAsyncTask() 方法进行导出
     * @throws Exception
     */
    public function getResult(): array
    {
        if (empty($this->result_data)) {
            throw new Exception('未导出任何数据');
        }

        return [$this->result_type, $this->result_data];
    }
}
